/*
   Copyright 2009 Abdulla Abdurakhmanov (abdulla@latestbit.com)
   Original sources are available at www.latestbit.com

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

#ifndef TIMER_HPP
#define TIMER_HPP

#include "cpputils/CppUtils.hpp"

namespace cpputils {
	namespace ept {

		/**
		 * Timer provides functionality for a time-based asynch operation
		 * (which must be running after some time)
		 *
		 * @par Usage:
		 * @code
		 * Timer* myTimer = new Timer();
		 * Timer::TimerId myTimerId = 1;
		 * myTimer->schedule(
		 *      myTimerId,
		 *		10000, // Handler must be called after 10 sec
		 *		boost::bind(&MyClass::myTimerHandler, this)
		 * );
		 * @endcode
		 */
		class Timer : public virtual RefObject {
		public:
			typedef long TimerId;
			typedef boost::shared_ptr < Timer > SharedPtr;
			typedef boost::function1 < void, TimerId > Handler;
			typedef boost::function2 < void, TimerId, RefObject* > HandlerWithRefObject;
		public:

			CPPU_DLLPRFX Timer(const std::string& sTimerName="", bool bStartImmediate = true);
			CPPU_DLLPRFX ~Timer();

			/**
			 * Scheduling a timer with specified delay, timer id, and handler
			 * @param nTimerId Timer Identifier.
			 * @param nDelay Time in milliseconds for delay call
			 * @param pfHandler Timer handler
			 * @note If timer id is already exists (already scheduled earlier), it's rescheduled with new params.
			 */
			CPPU_DLLPRFX void schedule(TimerId nTimerId, unsigned int nDelay, Handler const & pfHandler);

			/**
			 * Scheduling a timer with specified delay, timer id, handler and user object with base as RefObject
			 * @param nTimerId Timer Identifier.
			 * @param nDelay Time in milliseconds for delay call
			 * @param pfHandler Timer handler
			 * @param poRefObject Any user object with parent class as ref object
			 * @note If timer id is already exists (already scheduled earlier), it's rescheduled with new params.
			 */
			CPPU_DLLPRFX void schedule(TimerId nTimerId, unsigned int nDelay, HandlerWithRefObject const & pfHandler, RefObject* poRefObject);


			/**
			 * Canceling timer specified by timer id
			 */
			CPPU_DLLPRFX void cancel(TimerId nTimerId);

			/**
			 * Canceling all timers
			 */
			CPPU_DLLPRFX void cancelAll();

			/**
			 * Get the name of this timer
			 */
			CPPU_DLLPRFX const std::string& getTimerName();

		private:
			void start();
			void stop();
			void run();


		private:
			class TimerItem {
			public:
				inline TimerItem():m_nTimerId(0), m_pfHandler(NULL) {};

				inline TimerItem(TimerId nTimerId, Handler pfHandler)
						:m_nTimerId(nTimerId), m_pfHandler(pfHandler),
						m_pfHandlerWithRefObject(NULL), m_poRefObject(NULL) {};

				inline TimerItem(TimerId nTimerId, HandlerWithRefObject pfHandlerWithObject, RefObject* poRefObject)
						:m_nTimerId(nTimerId), m_pfHandler(NULL),
						m_pfHandlerWithRefObject(pfHandlerWithObject), m_poRefObject(poRefObject) {
					if(m_poRefObject!=NULL) {
						m_poRefObject->addRef();
					}
				}

				inline ~TimerItem() {
					if(m_poRefObject)
						m_poRefObject->release();
				}

				inline TimerItem(const TimerItem& src) { copyFrom(src); }
				inline void operator = (const TimerItem& src) { copyFrom(src); }

				inline TimerId const& getTimerId() const { return m_nTimerId; }
				inline bool equalToTaskId (TimerId nTimerId) { return getTimerId() == nTimerId; }

				inline const Handler& getHandler() const { return m_pfHandler; }
				inline void executeHandler() { m_pfHandler(m_nTimerId); };

				inline const HandlerWithRefObject& getHandlerWithRefObject() const { return m_pfHandlerWithRefObject; }
				inline void executeHandlerWithRefObject() { m_pfHandlerWithRefObject ( m_nTimerId, m_poRefObject ); };

			private:
				inline void copyFrom(const TimerItem& src) {
					m_nTimerId = src.getTimerId();
					m_pfHandler = src.m_pfHandler;
					m_pfHandlerWithRefObject = src.m_pfHandlerWithRefObject;
					m_poRefObject = src.m_poRefObject;
					if(m_poRefObject!=NULL)
						m_poRefObject->addRef();
				}
			private:
				TimerId m_nTimerId;
				Handler m_pfHandler;
				HandlerWithRefObject m_pfHandlerWithRefObject;
				RefObject* m_poRefObject;
			};

		private:
			void schedule(TimerId nTimerId, unsigned int nDelay, TimerItem& oItem);

		private:
			static const unsigned int AWAIT_TIMER_TIMEOUT = 500; // 500 msec is default timeout for wait any timer item

		private:
			typedef std::map < hrtime_t, TimerItem > Timers;
			Timers m_mTimers;
			typedef std::map < TimerId, Timers::iterator > TimerIds;
			TimerIds m_mTimerIds;

			boost::recursive_mutex m_mxTimers;

			hrtime_t m_nSystemFreq;

			boost::thread* m_poTimerThread;
			bool m_bStopFlag;

			const std::string m_sTimerName;
		};

	}
}

#endif
